#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <liburing.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>


/* This is x86 specific */
#define read_barrier()  __asm__ __volatile__("":::"memory")
#define write_barrier() __asm__ __volatile__("":::"memory")

#define BLOCK_SZ 1024

// 应用请求队列环形缓冲区
// submission queue 
struct app_io_sq_ring
{
    __u32* head;
    __u32* tail;
    __u32* ring_mask;
    __u32* ring_entries;
    __u32* flags;
    __u32* array;
};

// 应用完成队列环形缓冲区
// completion queue ring
struct app_io_cq_ring
{
    __u32* head;
	__u32* tail;
	__u32* ring_mask;
	__u32* ring_entries;
	__u32* cqes;
};

//
struct app_submitter
{
    int iIORingFd;
    struct app_io_sq_ring stSQRing;
    struct io_uring_sqe* pstSQEs;
    struct app_io_cq_ring stCQRing;
};

struct file_info {
    off_t file_sz;
    struct iovec iovecs[];      /* Referred by readv/writev */
};

void ShowURingParams(struct io_uring_params* pstParams)
{
    printf("sq_entries          : %u\n", pstParams->sq_entries);
    printf("cq_entries          : %u\n", pstParams->cq_entries);
    printf("flags               : 0x%x\n", pstParams->flags);
    printf("sq_thread_cpu       : %u\n", pstParams->sq_thread_cpu);
    printf("sq_thread_idle      : %u\n", pstParams->sq_thread_idle);
    printf("features            : 0x%x\n", pstParams->features);
    printf("wq_fd               : %u\n", pstParams->wq_fd);
    
    printf("io_sqring_offsets\n");
    printf(" sq_off.head        : %u\n", pstParams->sq_off.head);
    printf(" sq_off.tail        : %u\n", pstParams->sq_off.tail);
    printf(" sq_off.ring_mask   : %u\n", pstParams->sq_off.ring_mask);
    printf(" sq_off.ring_entries: %u\n", pstParams->sq_off.ring_entries);
    printf(" sq_off.flags       : 0x%x\n", pstParams->sq_off.flags);
    printf(" sq_off.dropped     : %u\n", pstParams->sq_off.dropped);
    printf(" sq_off.array       : %u\n", pstParams->sq_off.array);

    printf("io_cqring_offsets\n");
    printf(" cq_off.head        : %u\n", pstParams->cq_off.head);
    printf(" cq_off.tail        : %u\n", pstParams->cq_off.tail);
    printf(" cq_off.ring_mask   : %u\n", pstParams->cq_off.ring_mask);
    printf(" cq_off.ring_entries: %u\n", pstParams->cq_off.ring_entries);
    printf(" cq_off.overflow    : %u\n", pstParams->cq_off.overflow);
    printf(" cq_off.cqes        : %u\n", pstParams->cq_off.cqes);
    printf(" cq_off.flags       : 0x%x\n", pstParams->cq_off.flags);
}


/*
 * 返回传入其打开文件描述符的文件的大小。
 * 正确处理常规文件和块设备。
 * */

off_t get_file_size(int fd) {
    struct stat st;

    if(fstat(fd, &st) < 0) {
        perror("fstat");
        return -1;
    }
    if (S_ISBLK(st.st_mode)) {
        unsigned long long bytes;
        if (ioctl(fd, BLKGETSIZE64, &bytes) != 0) {
            perror("ioctl");
            return -1;
        }
        return bytes;
    } else if (S_ISREG(st.st_mode))
        return st.st_size;

    return -1;
}

int CAT_iouring_Setup(struct app_submitter* pstSubmitter)
{
    struct io_uring_params stParams;
    int iFd = io_uring_setup(2, &stParams);
    if (iFd == -1)
    {
        printf("io_uring_setup error, %s\n", strerror(errno));

        return -1;
    }
    
    ShowURingParams(&stParams);


    void* ptr = mmap(0, stParams.sq_off.array + stParams.sq_entries * sizeof(__u32), 
               PROT_READ|PROT_WRITE, 
               MAP_SHARED|MAP_POPULATE, 
               iFd, 
               IORING_OFF_SQ_RING);
    if (ptr == MAP_FAILED)
    {
        close(iFd);
        printf("mmap error, %s\n", strerror(errno));

        return -1;
    }
    
    void* pSQEntries = mmap(0, stParams.sq_entries * sizeof(struct io_uring_sqe),
                            PROT_READ|PROT_WRITE, MAP_SHARED|MAP_POPULATE,
                            iFd, IORING_OFF_SQES);
    if (pSQEntries == MAP_FAILED)
    {
        close(iFd);
        printf("mmap for submission queue entries error, %s\n", strerror(errno));

        return -1;
    }

    void* pCQEntries = mmap(0, stParams.cq_off.cqes + stParams.cq_entries * sizeof(struct io_uring_cqe),
                             PROT_READ|PROT_WRITE, MAP_SHARED|MAP_POPULATE, 
                             iFd, IORING_OFF_CQ_RING);
    if (pCQEntries == MAP_FAILED)
    {
        close(iFd);
        printf("mmap for completion queue entries error, %s\n", strerror(errno));

        return -1;
    }

    pstSubmitter->iIORingFd = iFd;
    pstSubmitter->pstSQEs = pSQEntries;

    pstSubmitter->stSQRing.head = ptr + stParams.sq_off.head;
    pstSubmitter->stSQRing.tail = ptr + stParams.sq_off.tail;
    pstSubmitter->stSQRing.ring_mask = ptr + stParams.sq_off.ring_mask;
    pstSubmitter->stSQRing.ring_entries = ptr + stParams.sq_off.ring_entries;
    pstSubmitter->stSQRing.flags = ptr + stParams.sq_off.flags;
    pstSubmitter->stSQRing.array = ptr + stParams.sq_off.array;

    pstSubmitter->stCQRing.head = ptr + stParams.cq_off.head;
    pstSubmitter->stCQRing.tail = ptr + stParams.cq_off.tail;
    pstSubmitter->stCQRing.ring_mask = ptr + stParams.cq_off.ring_mask;
    pstSubmitter->stCQRing.ring_entries = ptr + stParams.cq_off.ring_entries;
    pstSubmitter->stCQRing.cqes = ptr + stParams.cq_off.cqes;

    return 0;
}


int CAT_iouring_Submit(const char* pcFileName, struct app_submitter* pstSubmitter)
{
    // 
    int iFd = open(pcFileName, O_RDONLY);
    if (iFd == -1)
    {
        printf("open  %s error, %s\n", pcFileName, strerror(errno));
        return -1;
    }

    void *buf;
    off_t file_sz = get_file_size(iFd);
    if (file_sz < 0)
    {
        return 1;
    }

    off_t bytes_remaining = file_sz;
    int blocks = (int) file_sz / BLOCK_SZ;
    if (file_sz % BLOCK_SZ) blocks++;
    struct file_info* fi;
    fi = malloc(sizeof(*fi) + sizeof(struct iovec) * blocks);
    if (!fi) {
        fprintf(stderr, "Unable to allocate memory\n");
        return 1;
    }

    fi->file_sz = file_sz;

    /*
     * 对于我们需要读取的文件的每个块，我们分配一个iovec struct
     * 索引到 iovecs 数组中。这个数组作为一部分提交传入。
     * 如果你不明白这一点，那么你需要去
     * 了解一下 readv() 和 writev() 系统调用的工作方式。
     * */
    unsigned index = 0, current_block = 0, tail = 0, next_tail = 0;
    while (bytes_remaining) {
        off_t bytes_to_read = bytes_remaining;
        if (bytes_to_read > BLOCK_SZ)
            bytes_to_read = BLOCK_SZ;

        fi->iovecs[current_block].iov_len = bytes_to_read;

        void *buf;
        if( posix_memalign(&buf, BLOCK_SZ, BLOCK_SZ)) {
            perror("posix_memalign");
            return 1;
        }
        fi->iovecs[current_block].iov_base = buf;

        current_block++;
        bytes_remaining -= bytes_to_read;
    }

    next_tail = tail = *pstSubmitter->stSQRing.tail;
    next_tail++;
    read_barrier();
    
    index = tail & *pstSubmitter->stSQRing.ring_mask;
    struct io_uring_sqe* pstSQEntry = &pstSubmitter->pstSQEs[index];
    pstSQEntry->fd = iFd;
    pstSQEntry->flags = 0;
    pstSQEntry->opcode = IORING_OP_READV;
    pstSQEntry->addr = (unsigned long)fi->iovecs;
    pstSQEntry->len = blocks;
    pstSQEntry->off = 0;
    pstSQEntry->user_data = (__u64)fi;
    

    pstSubmitter->stSQRing.array[index] = index;
    tail = next_tail;

    if (*pstSubmitter->stSQRing.tail != tail)
    {
        *pstSubmitter->stSQRing.tail = tail;
        write_barrier();
    }

    int iRet = io_uring_enter(pstSubmitter->iIORingFd, 1, 1, IORING_ENTER_GETEVENTS, NULL);
    if (iRet < 0)
    {
        printf("io_uring_enter error, %s\n", strerror(errno));
        return -1;
    }

    return 0;
}


void output_to_console(char *buf, int len) {
    while (len--) {
        fputc(*buf++, stdout);
    }
}

void read_from_cq(struct app_submitter *pstSubmitter) {
    struct file_info *fi;
    struct app_io_cq_ring *cring = &pstSubmitter->stCQRing;
    struct io_uring_cqe *cqe;
    unsigned head, reaped = 0;

    head = *cring->head;

    do {
        read_barrier();
        /*
         * 请记住，这是一个环形缓冲区。如果头==尾，则表示
         * 缓冲区为空。
         * */
        if (head == *cring->tail)
            break;

        /* 获取条目 */
        cqe = &cring->cqes[head & *pstSubmitter->stCQRing.ring_mask];
        fi = (struct file_info*) cqe->user_data;
        if (cqe->res < 0)
            fprintf(stderr, "Error: %s\n", strerror(abs(cqe->res)));

        int blocks = (int) fi->file_sz / BLOCK_SZ;
        if (fi->file_sz % BLOCK_SZ) blocks++;

        for (int i = 0; i < blocks; i++)
            output_to_console(fi->iovecs[i].iov_base, fi->iovecs[i].iov_len);

        head++;
    } while (1);

    *cring->head = head;
    write_barrier();
}

int main(int argc, char** argv)
{
    struct app_submitter stSubmitter = {};
    struct io_uring_params stParams;


    int iRet = CAT_iouring_Setup(&stSubmitter);
    if (iRet == -1)
    {
        return 0;
    }

    CAT_iouring_Submit(argv[1], &stSubmitter);

    read_from_cq(&stSubmitter);
    
    return 0;
}